package com.wingsboat.common.protect.aspect;

import com.wingsboat.common.core.constant.CacheConstants;
import com.wingsboat.common.core.exception.IdempotentException;
import com.wingsboat.common.protect.annotation.Idempotent;
import com.wingsboat.common.protect.keyresolver.IdempotentKeyResolver;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;


/**
 * 幂等操作处理
 *
 * @author wangzhong
 */
@Aspect
@Component
public class IdempotentAspect {
    private static final Logger log = LoggerFactory.getLogger(IdempotentAspect.class);
    /**
     * IdempotentKeyResolver 集合
     */
    private Map<Class<? extends IdempotentKeyResolver>, IdempotentKeyResolver> keyResolvers = new ConcurrentHashMap<>();

    @Resource
    private RedissonClient redissonClient;

    /**
     * 注入 幂等 Key 解析器
     * @param idempotentKeyResolver
     */
    public void addIdempotentKeyResolver(IdempotentKeyResolver idempotentKeyResolver){
        keyResolvers.put(idempotentKeyResolver.getClass(),idempotentKeyResolver);
    }

    @Around(value = "@annotation(idempotent)")
    public Object aroundPointCut(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        // 获得 IdempotentKeyResolver
        IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver());
        Assert.notNull(keyResolver, "找不到对应的 IdempotentKeyResolver");
        // 解析 Key
        String key = keyResolver.resolver(joinPoint, idempotent);

        // 1. 锁定 Key
        String redisKey = CacheConstants.IDEMPOTENT+key;
        RLock lock = redissonClient.getLock(redisKey);
        boolean success = lock.tryLock(3, 100, TimeUnit.SECONDS);
        // 锁定失败，抛出异常
        if (!success) {
            log.info("[aroundPointCut][方法({}) 参数({}) 存在重复请求]", joinPoint.getSignature().toString(), joinPoint.getArgs());
            throw new IdempotentException(idempotent.message());
        }

        // 2. 执行逻辑
        try {
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            if (idempotent.deleteKeyWhenException()) {
                if (idempotent.deleteKeyWhenException()) {
                    if (lock != null && lock.isLocked()) {
                        lock.unlock();
                    }
                }
            }
            throw throwable;
        }
    }

}
